package top.miay.snowid;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @description: snowid-generator id生成器
 * @author 猿猿长成记
 */
public class SnowIdGenerator {
    private static long TIMESTAMP_BITS;
    private static long MACHINE_ID_BITS;
    private static long DC_BITS;
    private static long SEQUENCE_BITS;

    private static long MACHINE_ID;
    /**
     * 设置后最长可运行34年+，暂不做校验
     * */
    private static long MAX_TIMESTAMP;
    private static long MAX_SEQUENCE;

    private static long TIMESTAMP_SHIFT;
    private static long MACHINE_ID_SHIFT;

    private static int CLOCK_BACK_TIME;
    private static int CLOCK_BACK_TIME_AND_BITS;
    private static long EPOCH_TIME;

    private static Map<String, Generator> GENERATORS = new HashMap<>();

    /**
     * 将配置文件初始化至生成器工具类
     * */
    public static void init(SnowIdGeneratorConfig snowIdGeneratorConfig){
        SnowIdGeneratorConfig.AppInfo appInfo = snowIdGeneratorConfig.getAppInfo();
        TIMESTAMP_BITS = appInfo.getTimStampBits();
        MACHINE_ID_BITS = appInfo.getMachineIdBits();
        DC_BITS = appInfo.getDcBits();
        SEQUENCE_BITS = appInfo.getSequenceBits();

        MACHINE_ID = appInfo.getMachineId();
        MAX_TIMESTAMP = (1L <<TIMESTAMP_BITS) - 1;
        MAX_SEQUENCE = (1L <<SEQUENCE_BITS) -1;

        CLOCK_BACK_TIME = snowIdGeneratorConfig.getClockBackTime();
        CLOCK_BACK_TIME_AND_BITS = CLOCK_BACK_TIME -1;
        EPOCH_TIME = SnowIdUtil.getTimeSecond(snowIdGeneratorConfig.getEpochTime());

        TIMESTAMP_SHIFT = MACHINE_ID_BITS + SEQUENCE_BITS;
        MACHINE_ID_SHIFT = SEQUENCE_BITS;

        List<SnowIdGeneratorConfig.Id> ids = snowIdGeneratorConfig.getAppInfo().getIds();
        for(SnowIdGeneratorConfig.Id id : ids){
            Generator generator = new Generator();
            generator.setName(id.getName());
            AtomicLong[] timeSequenceCache = new AtomicLong[CLOCK_BACK_TIME];
            for(int i = 0; i < CLOCK_BACK_TIME; i++){
                timeSequenceCache[i] = new AtomicLong(0);
            }
            long currentTimeSecond = SnowIdUtil.getCurrentTimeSecond();
            long[] timeCache = new long[CLOCK_BACK_TIME];
            for(int i = 0; i < CLOCK_BACK_TIME; i++){
                timeCache[i] = currentTimeSecond;
            }
            generator.setTimeSequenceCache(timeSequenceCache);
            generator.setTimeCache(timeCache);
            GENERATORS.put(generator.getName(), generator);
        }
    }

    /**
     * 默认生成方式
     * */
    public static long generate(String name){
        Generator generator = GENERATORS.get(name);
        if(generator == null){
            throw new SnowIdException("Generator not found %s.", new Object[]{name});
        }
        return generator.generate();
    }

    /**
     * 指定格式化的生成方式
     * */
    public static InnerInfo generate(String name, SnowIdFormat snowIdFormat){
        Generator generator = GENERATORS.get(name);
        if(generator == null){
            throw new SnowIdException("Generator not found %s.", new Object[]{name});
        }
        return generator.generate(snowIdFormat);
    }

    public static class Generator{
        private String name;
        private AtomicLong[] timeSequenceCache;
        private long[] timeCache;
        private final ReentrantLock lock = new ReentrantLock();

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public AtomicLong[] getTimeSequenceCache() {
            return timeSequenceCache;
        }

        public void setTimeSequenceCache(AtomicLong[] timeSequenceCache) {
            this.timeSequenceCache = timeSequenceCache;
        }

        public long[] getTimeCache() {
            return timeCache;
        }

        public void setTimeCache(long[] timeCache) {
            this.timeCache = timeCache;
        }

        public long generate() {
            return innerGenerate().getId();
        }

        public InnerInfo generate(SnowIdFormat snowIdFormat){
            InnerInfo innerInfo = innerGenerate();
            innerInfo.setIdStr(snowIdFormat.format(innerInfo));
            return innerInfo;
        }

        public InnerInfo innerGenerate(){
            long currentTimeSecond = SnowIdUtil.getCurrentTimeSecond();
            //获取索引值
            int index = Math.toIntExact(currentTimeSecond & SnowIdGenerator.CLOCK_BACK_TIME_AND_BITS);
            long time = currentTimeSecond - EPOCH_TIME;
            long seq = -1;

            try{
                lock.lock();
                if(timeCache[index] == currentTimeSecond){
                    //2.判断时间戳一致则继续序号生成
                    seq = timeSequenceCache[index].incrementAndGet();
                }else if(timeCache[index]<currentTimeSecond){
                    //3.时间戳大于原时间戳
                    timeCache[index] = currentTimeSecond;
                    timeSequenceCache[index].set(0);
                    seq = 0;
                }else{
                    //4.当前时间戳小于索引位时间戳，代表时间回拨超出可处理范围，抛出异常
                    throw new SnowIdException("snowid generator has a time rollback and incompatible processing.");
                }
            }finally {
                lock.unlock();
            }

            if(seq>SnowIdGenerator.MAX_SEQUENCE){
                //超出最大值抛出异常
                throw new SnowIdException("snowid generator '%s' the current time exceeds the maximum sequence number %s.", new Object[]{seq, MAX_SEQUENCE});
            }
            long id = time << TIMESTAMP_SHIFT
                    | MACHINE_ID
                    | seq;

            return new InnerInfo(id, time, seq);
        }
    }

    public static class InnerInfo{
        private long timestamp;
        private long sequence;
        private long id;
        private String idStr;

        public InnerInfo(long id, long timestamp, long sequence) {
            this.id = id;
            this.timestamp = timestamp;
            this.sequence = sequence;
        }

        public long getTimestamp() {
            return timestamp;
        }

        public void setTimestamp(long timestamp) {
            this.timestamp = timestamp;
        }

        public long getSequence() {
            return sequence;
        }

        public void setSequence(long sequence) {
            this.sequence = sequence;
        }

        public long getId() {
            return id;
        }

        public void setId(long id) {
            this.id = id;
        }

        public String getIdStr() {
            return idStr;
        }

        public void setIdStr(String idStr) {
            this.idStr = idStr;
        }
    }

    /**
     * 可扩展格式化需求，默认支持25字符窜的可读序列号
     * */
    public enum SnowIdFormat{
        /**
         * 25位字符窜
         * */
        READABLE{
            private LocalDateTime EPOCH_TIME_CACHE;
            public String format(InnerInfo innerInfo){
                if(EPOCH_TIME_CACHE == null){
                    synchronized (this){
                        EPOCH_TIME_CACHE = SnowIdUtil.getLocalDateTime(EPOCH_TIME);
                    }
                }
                return EPOCH_TIME_CACHE.plusSeconds(innerInfo.getTimestamp()).format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))
                        +String.format("%3s",Long.toHexString(MACHINE_ID>>MACHINE_ID_SHIFT)).replace(" ", "0")
                        +String.format("%8s", Long.toHexString(innerInfo.getSequence())).replace(" ", "0");
            }
        };

        public abstract String format(InnerInfo innerInfo);
    }
}
